package threadutils;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;

public final class RecurrentThread {

    /** The task to run in a new thread. */
    private Runnable mTask;

    /** Lock used for modifying the task. */
    private final ReentrantLock mTaskLock;

    /** The thread running the task. */
    private RecurrentThreadInstance mThread;

    /** Lock used for modifying the thread. */
    private final ReentrantLock mThreadLock;

    public RecurrentThread() {
        this(null);
    }

    public RecurrentThread(Runnable task) {
        mTask = task;
        mThread = null;
        mTaskLock = new ReentrantLock();
        mThreadLock = new ReentrantLock();
    }

    public void setTask(Runnable task) {
        mTaskLock.lock();
        mTask = task;
        mTaskLock.unlock();
    }

    public Thread getCurrentThread() {
        mThreadLock.lock();
        Thread returnValue = mThread;
        mThreadLock.unlock();

        return returnValue;
    }

    public Thread start() throws DuplicateThreadException, IllegalStateException {
        mTaskLock.lock();
        mThreadLock.lock();

        //System.out.println("testing mTask...");
        if (mTask == null) {
            throw new IllegalStateException("Task must be set.");
        }
        //System.out.println("testing mThread (" + mThread + ") " + (mThread != null ? mThread.isRunning() : ""));
        if (mThread != null && mThread.isRunning()) {
            throw new DuplicateThreadException();
        }

        //System.out.println("Creating new mThread...");
        Thread returnValue = mThread = new RecurrentThreadInstance(mTask);
        //System.out.println("Starting mThread...");
        mThread.start();
        mThread.mIsRunning.set(true);

        mTaskLock.unlock();
        mThreadLock.unlock();

        return returnValue;
    }

    private class RecurrentThreadInstance extends Thread {

        private Runnable mTask;

        private final AtomicBoolean mIsRunning;

        public RecurrentThreadInstance(Runnable task) {
            mTask = task;
            mIsRunning = new AtomicBoolean(false);
        }

        @Override
        public void run() {
            mIsRunning.set(true);
            mTask.run();
            mIsRunning.set(false);
        }

        public boolean isRunning() {
            return mIsRunning.get();
        }
    }

    public static class DuplicateThreadException extends Exception {
        public DuplicateThreadException() {
            super("Cannot start another instance of the thread while the previous instance is still running.");
        }
    }
}
